package com.maiko.usercenter.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.maiko.usercenter.common.ErrorCode;
import com.maiko.usercenter.common.SqlUtils;
import com.maiko.usercenter.constant.CommonConstant;
import com.maiko.usercenter.constant.RedisKeyConstants;
import com.maiko.usercenter.constant.RedisLockConstants;
import com.maiko.usercenter.constant.UserConstant;
import com.maiko.usercenter.exception.BusinessException;
import com.maiko.usercenter.lock.LockExecutor;
import com.maiko.usercenter.mapper.UserMapper;
import com.maiko.usercenter.model.dto.UserAccountLoginRequest;
import com.maiko.usercenter.model.dto.UserAccountRegisterRequest;
import com.maiko.usercenter.model.dto.UserPageQueryRequest;
import com.maiko.usercenter.model.entity.User;
import com.maiko.usercenter.model.vo.LoginUserVO;
import com.maiko.usercenter.service.UserService;
import com.maiko.usercenter.utils.RegexUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
* @author 73450
* @description 针对表【user(用户表)】的数据库操作Service实现
* @createDate 2025-07-12 15:26:09
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService {

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private LockExecutor lockExecutor;

    @Resource
    private RedisTemplate redisTemplate;


    /**
     * 多次查询 + 插入操作应放在一个事务中，否则在高并发下可能出现数据竞争导致账号/手机/邮箱重复插入。
     * 建议：添加事务注解 @Transactional 来保证原子性。
     */
    @Override
//    @Transactional(rollbackFor = Exception.class)
    public long userRegister(UserAccountRegisterRequest userAccountRegisterRequest) {

        // 1. 从请求体中获取用户注册参数
        String account = userAccountRegisterRequest.getAccount();
        String password = userAccountRegisterRequest.getPassword();
        String checkPassword = userAccountRegisterRequest.getCheckPassword();
        String phone = userAccountRegisterRequest.getPhone();
        String email = userAccountRegisterRequest.getEmail();
        Integer gender = userAccountRegisterRequest.getGender();


        /**
         * StrUtil.hasBlank(...) 只判断是否包含空白字符，不能有效防止参数为 null 的情况。
         * if (StrUtil.hasBlank(account, password, checkPassword, phone, email) || gender == null) {
         *             throw new BusinessException(ErrorCode.NULL_ERROR, "注册参数不能为空");
         * }
         * 如果 account、password 等字段是 null，调用 .length() 会抛出 NullPointerException。
         * 建议：使用更安全的判空方式（如 Apache Commons 或自定义工具类）
         */

        // 2.校验
        // 2.1 非空检查（一般通过@Valid完成，保险起见再校验一次）
        if (StringUtils.isAnyBlank(account, password, checkPassword, phone, email) || gender == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "注册参数不能为空");
        }
        // 2.2 账号密码格式校验
        if (account.length() < 4 || account.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度必须在 4~20 位之间");
        }
        // 2.3 密码格式校验
        if (password.length() < 6 || password.length() > 16) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度必须在 6~16 位之间");
        }
        //  2.4 手机号格式校验（中国大陆）
       if (RegexUtils.isPhoneInvalid(phone)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号格式不正确");
       }
       // 2.5 邮箱格式校验
       if (RegexUtils.isEmailInvalid(email)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式不正确");
       }

        // 2.6 密码一致性校验
        if (!password.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PASSWORD_ERROR);
        }
        // 2.7 性别取值校验（假设 0=未知，1=男，2=女）
        if (gender < 0 || gender > 2) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "性别参数不合法");
        }


//        queryWrapper.eq("account", account);
//        long count = this.count(queryWrapper);
//        if (count > 0) {
//            throw new BusinessException(ErrorCode.USER_ACCOUNT_EXIST);
//        }
//        if (this.count(new QueryWrapper<User>().eq("phone", phone)) > 0) {
//            throw new BusinessException(ErrorCode.USER_PHONE_EXIST);
//        }
//        if (this.count(new QueryWrapper<User>().eq("email", email)) > 0) {
//            throw new BusinessException(ErrorCode.USER_EMAIL_EXIST);
//        }
        /**
         * 对 account、phone、email 分别执行三次 count 查询，增加了数据库负担，且容易被并发攻击利用时间差重复注册。
         * 建议：合并查询为一次，减少数据库交互次数。
         *
         * 上面抛出“账号已存在”、“手机号已存在”等明确提示可能被用于枚举用户信息，增加安全风险。
         * 建议：统一返回模糊错误信息，避免泄露系统细节。
         */

        /**
         * synchronized (account.intern()) {
         *     // 注册逻辑
         * }
         * 是为了防止并发注册时出现“同一个账号被重复注册”的线程安全问题，使用了 Java 中的 intern() 字符串池锁机制。
         * 这就不用@Transactional加在整个类上了。
         */
//        synchronized (account.intern()){
//            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//            queryWrapper.eq("account", account)
//                    .or()
//                    .eq("phone", phone)
//                    .or()
//                    .eq("email", email);
//
//            if (this.count(queryWrapper) > 0) {
//                throw new BusinessException(ErrorCode.USER_EXIST, "账号、手机号或邮箱已被注册");
//            }
//            // 7. 密码加密（使用 BCrypt，安全性更高）
//            String encryptPassword = passwordEncoder.encode(password);
//            // 8. 创建用户对象
//            User user = new User();
//            user.setAccount(account);
//            user.setPassword(encryptPassword);
//            user.setPhone(phone);
//            user.setEmail(email);
//            user.setGender(gender);
//            // 这条执行完，MyBatis-Plus底层会自动将生成的主键 ID 回填到实体对象的 id 字段，所以能直接user.getId();
//            boolean save = this.save(user);
//            if (!save) {
//                throw new BusinessException(ErrorCode.OPERATION_ERROR, "注册失败，请再试一下或者联系管理员");
//            }
//            return user.getId();
//        }

        /**
         * 🌟 锁粒度可能过大	account.intern() 会锁住整个常量池中的那个字符串引用，虽然可控，但如果账号很多，可能对性能有影响
         * 🚨 字符串常量池泄漏风险	如果使用大量不同的账号注册，每次都 intern()，会将很多字符串写入方法区（或元空间），有可能造成内存占用过大（JDK8 后影响降低）
         * ❗ 锁只在当前服务生效	在分布式环境中，如果有多个服务节点，这个 synchronized 是不够的，需要使用 Redis 分布式锁（比如用 Redisson）代替
         */
        // 3. 调用 LockExecutor 执行加锁逻辑
        return lockExecutor.execute(RedisLockConstants.USER_REGISTER_LOCK + account, () -> {
            // 检查账号、手机号、邮箱是否已存在
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("account", account)
                    .or()
                    .eq("phone", phone)
                    .or()
                    .eq("email", email);

            if (this.count(queryWrapper) > 0) {
                throw new BusinessException(ErrorCode.USER_EXIST, "账号、手机号或邮箱已被注册");
            }

            // 密码加密
            String encryptPassword = passwordEncoder.encode(password);

            // 创建用户实体
            User user = new User();
            user.setAccount(account);
            user.setPassword(encryptPassword);
            user.setPhone(phone);
            user.setEmail(email);
            user.setGender(gender);

            // 保存用户
            boolean saved = this.save(user);
            if (!saved) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "注册失败，请再试一下或者联系管理员");
            }
            // 注册成功后写入缓存
            String redisKey = RedisKeyConstants.USER_INFO_CACHE_KEY + user.getId();
            redisTemplate.opsForValue().set(redisKey, user, 1, TimeUnit.HOURS);
            return user.getId();
        });
    }

    @Override
    public LoginUserVO userLogin(UserAccountLoginRequest userAccountLoginRequest, HttpServletRequest request) {
        // 1. 从请求体中获取用户名和密码
        String account = userAccountLoginRequest.getAccount();
        String password = userAccountLoginRequest.getPassword();

        // 2.校验

        // 2.1 非空校验
        if (StrUtil.hasBlank(account, password)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号或密码不能为空");
        }
        // 2.2 账号格式校验
        if (account.length() < 4 || account.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号长度必须在 4~20 位之间");
        }
        // 2.3 密码格式校验
        if (password.length() < 6 || password.length() > 16) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度必须在 6~16 位之间");
        }


//        // 3. 根据账号查询用户（假设账号是唯一的）
//        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("account", account);
//        User user = this.getOne(queryWrapper);
//        if (user == null) {
//            throw new BusinessException(ErrorCode.NULL_ERROR, "用户不存在");
//        }

        /**
         * 在 `MyBatis-Plus` 中，**推荐优先使用 `LambdaQueryWrapper`**，理由如下：
         * | 对比项     | `QueryWrapper`                   | `LambdaQueryWrapper`            |
         * | ---------- | -------------------------------- | ------------------------------- |
         * | 字段名写法 | 使用字符串 `"account"`           | 使用方法引用 `User::getAccount` |
         * | 类型安全   | ❌ 不安全，字段名写错编译不会报错 | ✅ 编译时检查，字段不存在会报错  |
         * | 易重构     | ❌ 重命名字段时不会自动修改       | ✅ 支持 IDE 自动重构             |
         * | 写法简洁   | 一般                             | 更清晰、类型安全                |
         */
        // 3. 根据账号查询用户（使用 LambdaQueryWrapper 避免字段名错误）
        User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getAccount, account));
        if (user == null) {
            log.warn("用户不存在: {}", account);
            throw new BusinessException(ErrorCode.NULL_ERROR, "用户不存在");
        }

        // 4. 验证密码（使用 BCrypt 的匹配方法）
        boolean matches = passwordEncoder.matches(password, user.getPassword());
        if (!matches) {
            throw new BusinessException(ErrorCode.PASSWORD_ERROR, "密码错误");
        }

        // 5. 登录成功后写入缓存
        String redisKey = RedisKeyConstants.USER_INFO_CACHE_KEY + user.getId();
        redisTemplate.opsForValue().set(redisKey, user, 1, TimeUnit.HOURS);

        // 6. 记录用户的登录态
        request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
        return this.getLoginUserVO(user);

    }

    /**
     * 用户退出登录功能
     * 此方法通过检查用户登录状态并移除该状态来实现用户退出登录的操作
     *
     * @param request HTTP请求对象，用于访问会话状态
     * @return 总是返回true，表示退出登录操作执行了
     * @throws BusinessException 如果用户未登录就尝试退出登录，抛出此异常
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        // 检查用户登录状态是否存在，如果不存在则抛出异常
        if (request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除登录态，执行退出登录操作
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        return true;
    }

    @Override
    public LoginUserVO getLoginUser(HttpServletRequest request) {
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        /**
         * 即使 currentUser 对象不为 null，仍然有可能用户对象已初始化但其中的 id 字段为 null。
         * id 通常是用户对象的唯一标识符，代表用户在系统中的身份。如果 id 为 null，这可能意味着当前用户对象无效或未正确加载。
         */
        if (currentUser == null || currentUser.getId() == null) {
            throw new BusinessException(ErrorCode.USER_NOT_LOGIN);
        }
        // 从数据库查询（追求性能的话可以走缓存）
//        User tempUser = this.getById(currentUser.getId());
//        if (tempUser == null) {
//            throw new BusinessException(ErrorCode.USER_NOT_EXIST);
//        }
//        return this.getLoginUserVO(tempUser);
        Long userId = currentUser.getId();
        String redisKey = RedisKeyConstants.USER_INFO_CACHE_KEY + userId;

        // 从缓存中获取用户信息
        User cachedUser = (User)redisTemplate.opsForValue().get(redisKey);
        if (cachedUser != null) {
            return this.getLoginUserVO(cachedUser);
        }
        // 缓存不存在，则从数据库查询
        User dbUser = this.getById(userId);
        if (dbUser == null) {
            throw new BusinessException(ErrorCode.USER_NOT_EXIST);
        }
        // 写入缓存，设置过期时间
        redisTemplate.opsForValue().set(redisKey, dbUser, 1, TimeUnit.HOURS);
        return this.getLoginUserVO(dbUser);

    }

    @Override
    public QueryWrapper<User> getQueryWrapper(UserPageQueryRequest userPageQueryRequest) {
        if (userPageQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = userPageQueryRequest.getId();
        String username = userPageQueryRequest.getUsername();
        String account = userPageQueryRequest.getAccount();
        Integer role = userPageQueryRequest.getRole();
        String sortField = userPageQueryRequest.getSortField();
        String sortOrder = userPageQueryRequest.getSortOrder();

        /**
         * SELECT *
         * FROM user
         * WHERE id=1
         *   AND username LIKE '%maiko%'
         *   AND account LIKE '%maiko_account%'
         *   AND role = 1
         * ORDER BY create_time ASC;
         */
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(id != null, "id", id);
        queryWrapper.like(StringUtils.isNotBlank(username), "username", username);
        queryWrapper.like(StringUtils.isNotBlank(account), "account", account);
        queryWrapper.eq(role != null, "role", role);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), sortField);
        return queryWrapper;


    }


    /**
     * 根据User对象获取LoginUserVO对象
     * 此方法用于将User实体类的对象转换为LoginUserVO值对象，便于在不同层次之间传递数据
     *
     * @param user User实体类对象，包含用户相关信息
     * @return LoginUserVO对象，其中包含了从User对象复制过来的属性
     * @throws BusinessException 当输入的User对象为空时抛出业务异常，提示请求参数为空
     */
    private LoginUserVO getLoginUserVO(User user) {
        // 检查输入的User对象是否为空，如果为空则抛出异常
        if (user == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "请求参数为空");
        }
        // 创建LoginUserVO对象，用于存储用户登录信息
        LoginUserVO loginUserVO = new LoginUserVO();
        // 使用Spring框架的BeanUtils工具类，将User对象的属性值复制到LoginUserVO对象中
        BeanUtils.copyProperties(user, loginUserVO);
        // 返回填充好的LoginUserVO对象
        return loginUserVO;
    }



}




